#!/usr/bin/python -S
"""
gen_dispatch.py

NOTES ON C/C++

pgi.c is a C interface.  It can be used with C or C++.

It needs to call a function with type pgi_dispatch_t, which is defined in terms
of C types.

pgi.c can be compiled as C++, in which case it will accept a function with a
namespace.  But we still have to return int and not bool.

We're generating C++ handlers now, so those return bool (as well as having C++
protobufs in their request and response signatures.)


TODO: 

  - Parametrize malloc
  - Parametrize stderr
    - There could be a TnetContext in app_ctx
    - Create PgiContext, which contains a TnetContext?
  - Send parse errors to stderr
"""

__author__ = 'Andy Chu'


import optparse
import os
import sys

import jsontemplate


# Should read the APP file, or get piped some JSON?

def CreateOptionsParser():
  parser = optparse.OptionParser()

  parser.add_option(
      '-a', '--applet-name', dest='applet_name', type='str', default='',
      help='Name of the applet')
  parser.add_option(
      '--namespace', dest='namespace', type='str',
      help='Protocol buffer namespace.')
  parser.add_option(
      '--handlers', dest='handlers', type='str', default='',
      help='List of comma-separated handler names.')
  parser.add_option(
      '-o', '--out-dir', dest='out_dir', type='str', default='.',
      help='Output directory.')
  # Hm, the default doesn't really make sense
  parser.add_option(
      '-l', '--language', dest='language', default='cpp',
      choices=['cpp', 'go'],
      help='Language to generate code in.')

  return parser


_H_TEMPLATE = """
// GENERATED -- DO NOT EDIT

#include <stdlib.h>  // size_t

// Hm this could be written.
// If you want to clean this up and use Buffer, you could include pgi.h here?

namespace {namespace} {

// pgi_dispatch_func_t echo_dispatch;
int {applet_name|lower}_dispatch(
    void* app_ctx,
    const char* route,
    const char* in_data, size_t in_length,
    char** out_data, size_t* out_length);

};  // namespace {namespace}
"""

_C_TEMPLATE = """
// GENERATED -- DO NOT EDIT

#include "dispatch.gen.h"  // my header

#include "{applet_name|lower}.gen.h"  // handler functions
#include "{proto_name}.pb.h"  // request/response types

#include <string.h>  // strlen
#include <string>  // C++ string

namespace {namespace} {

int {applet_name|lower}_dispatch(
    void* app_ctx,
    const char* route,
    const char* in_data, size_t in_length,
    char** out_data, size_t* out_length) {

  // dispatch
  // deserialize request
  // call handler function with typed request, and app context
  // get typed response
  // serialize it to out_*

  // This makes a copy, I don't think there's any way to avoid it?
  std::string req_str(in_data, in_length);
  std::string resp_str;

  if (0) {
    ;  // for easier code generation
{.repeated section handlers}
  } else if (strcmp(route, "{@}") == 0) {   {# TODO: c string escape?}
    {@}Request req;

    // TODO: check errors
    req.ParseFromString(req_str);

    {@}Response resp;
    {@}Handler(({ctx_name}*)app_ctx, req, &resp);

    resp.SerializeToString(&resp_str);
{.end}
  } else {
    // TODO: Fail: no such route
    *out_data = (char*)malloc(10);
    strcpy(*out_data, "foo\\n");
    *out_length = strlen(*out_data);
    return false;  // ERROR
  }

  // Now return response buffer.  We are supposed to allocate a new one, and the
  // caller frees, so use memcpy.
  size_t resp_len = resp_str.length();
  char* dest = (char*)malloc(resp_len);
  memcpy(dest, resp_str.c_str(), resp_len);

  // Return values
  *out_data = dest;
  *out_length = resp_len;
}

};  // namespace {namespace}
"""

_HANDLER_TEMPLATE = """
#include "{proto_name}.pb.h"

namespace {namespace} {

// The app should define an app context.
typedef struct {ctx_name}_s {ctx_name};

{.repeated section handlers}
bool {@}Handler(
  {ctx_name}* app_ctx,
  const {@}Request& request,
  {@}Response* response);
{.end}

};  // namespace {namespace}

"""

_GO_TEMPLATE = """
{.repeated section handlers}
  {@}
{.end}
"""


def main(argv):
  (options, argv) = CreateOptionsParser().parse_args(argv)

  handlers = [h for h in options.handlers.split(',') if h]
  if not handlers:
    raise RuntimeError(
        "At least one handler should be be specified with --handlers.")

  applet_name = options.applet_name
  data = {
      # Comes from APP file?  The Applet() name
      'applet_name': applet_name,
      'ctx_name': applet_name + 'App',
      # This could come from the APP file too, in a Proto() declaration?
      'proto_name': applet_name.lower(),

      # Comes from .proto file.  Need to convert from foo.bar.baz to
      # foo::bar::baz.
      'namespace': options.namespace,

      # Comes from APP file, from the list of Routes()
      'handlers': handlers,
      }

  if options.language == 'cpp':
    # Compiled as C++ because it has protobufs, etc.
    c_name = os.path.join(options.out_dir, 'dispatch.gen.cc')
    h_name = os.path.join(options.out_dir, 'dispatch.gen.h')
    hh_name = os.path.join(options.out_dir, 'echo.gen.h')

    c_out = open(c_name, 'w')
    h_out = open(h_name, 'w')
    hh_out = open(hh_name, 'w')

    print >>c_out, jsontemplate.expand(_C_TEMPLATE, data)
    print >>h_out, jsontemplate.expand(_H_TEMPLATE, data)
    print >>hh_out, jsontemplate.expand(_HANDLER_TEMPLATE, data)

  elif options.language == 'go':
    go_name = os.path.join(options.out_dir, 'dispatch.go')
    go_out = open(go_name, 'w')
    print >>go_out, jsontemplate.expand(_GO_TEMPLATE, data)

  else:
    raise AssertionError  # should be caught by options parser

  print >>sys.stderr, 'Done'
  return 0


if __name__ == '__main__':
  try:
    sys.exit(main(sys.argv))
  except RuntimeError, e:
    print >> sys.stderr, e.args[0]
    sys.exit(1)
